#pragma once
#include <iostream>
#include <thread>
#include <vector>
#include <cstring>

inline void custom_memcpy(char* destination, char* source, 
                         size_t length, int data_size, size_t stride) {
    for (size_t i = 0; i < length; i++) {
        std::memcpy(destination, source, data_size);
        destination += data_size;
        source += stride;
    }
}

void threaded_memory_copy(char* destination, char* source, 
                         size_t length, int data_size, size_t stride, int thread_count);

template<typename DestinationType, typename SourceType>
inline void custom_assign(DestinationType* destination, SourceType* source, 
                         size_t length, int interval) {
    int source_index = 0;
    for (size_t i = 0; i < length; i++) {
        destination[i] = source[source_index];
        source_index += interval;
    }
}

template<typename DestinationType, typename SourceType>
inline void threaded_assign(DestinationType* destination, SourceType* source, 
                           size_t length, size_t stride, int thread_count) {
    if (thread_count > static_cast<int>(length >> 8)) {
        thread_count = static_cast<int>(length >> 8);
    }
    if (thread_count < 1) thread_count = 1;
    
    stride /= sizeof(SourceType);
    size_t block_length = length / thread_count;
    size_t remainder = length % thread_count;
    size_t block_interval = block_length * stride;
    
    std::vector<std::thread> threads;
    threads.reserve(thread_count);
    
    for (int i = 0; i < thread_count - remainder; i++) {
        threads.emplace_back(custom_assign<DestinationType, SourceType>, 
                           destination, source, block_length, stride);
        destination += block_length;
        source += block_interval;
    }
    
    block_length++;
    block_interval += stride;
    for (int i = thread_count - remainder; i < thread_count; i++) {
        threads.emplace_back(custom_assign<DestinationType, SourceType>, 
                           destination, source, block_length, stride);
        destination += block_length;
        source += block_interval;
    }
    
    for (auto& thread : threads) {
        thread.join();
    }
}